package org.study.redisson.boot.controller;

import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.concurrent.TimeUnit;

/**
 * 参考https://www.cnblogs.com/wq-9/articles/16423575.html
 *
 * @author doveylovey
 * @version v1.0.0
 * @email 1135782208@qq.com
 * @date 2024年01月19日
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/lock")
public class RedissonLockController {
    private final RedissonClient redissonClient;
    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 可重入锁(ReentrantLock)
     * <p>
     * 如果负责储存这个分布式锁的Redisson节点宕机以后，而且这个锁正好处于锁住的状态时，这个锁会出现锁死的状态。
     * 为了避免这种情况的发生，Redisson内部提供了一个监控锁的看门狗，它的作用是在Redisson实例被关闭前，不断的延长锁的有效期。
     * 默认情况下，看门狗的检查锁的超时时间是30秒钟，也可以通过修改Config.lockWatchdogTimeout来另行指定。
     * 另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
     * <p>
     * 结论：
     * lock.lock()即没有指定锁的过期时间，就是用30s，即看门狗的默认时间，只要占锁成功，就会启动一个定时任务【重新给锁设置过期时间，新的过期时间就是看门狗的默认时间】，每隔10秒就会自动续期到30秒。
     * lock.lock(10, TimeUnit.SECONDS)，默认锁的过期时间就是我们指定的时间。
     */
    @ResponseBody
    @GetMapping("/hello1")
    public String hello1() {
        RLock lock = redissonClient.getLock("my-lock");
        try {
            // 加锁
            lock.lock();
            // 锁的自动续期，如果业务执行时间超长，运行期间会自动给锁续期30秒时间，不用担心业务时间长，锁自动过期
            // 加锁的业务只要运行完成，就不会给当前锁续期，即使不手动解锁，锁默认在30秒后也会自动删除
            System.out.println(Thread.currentThread().getId() + " 加锁成功，执行业务...");
            TimeUnit.SECONDS.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            System.out.println(Thread.currentThread().getId() + " 释放锁...");
            lock.unlock();
        }
        return "hello1";
    }

    @ResponseBody
    @GetMapping("/hello2")
    public String hello2() {
        RLock lock = redissonClient.getLock("my-lock");
        try {
            // 加锁
            // 设置的自动解锁时间一定要大于业务执行时间，因为在锁时间到了以后，不会自动续期
            lock.lock(10, TimeUnit.SECONDS);
            System.out.println(Thread.currentThread().getId() + " 加锁成功，执行业务...");
            TimeUnit.SECONDS.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
            System.out.println(Thread.currentThread().getId() + " 释放锁...");
            lock.unlock();
        }
        return "hello2";
    }

    /**
     * 读写锁：读锁
     * <p>
     * 基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。
     * 其中读锁和写锁都继承了RLock接口。
     * 分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。
     * <p>
     * 测试：
     * 先加写锁，后加读锁，此时并不会立刻给数据加读锁，而是需要等待写锁释放后，才能加读锁
     * 先加读锁，再加写锁：有读锁，写锁需要等待
     * 先加读锁，再加读锁：并发读锁相当于无锁模式，会同时加锁成功
     * 总结：只要有写锁的存在，都必须等待，写锁是一个排他锁，只能有一个写锁存在，读锁是一个共享锁，可以有多个读锁同时存在。
     */
    @GetMapping("/read")
    public Object readValue() {
        RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
        RLock rLock = lock.readLock();
        Object s = null;
        try {
            // 加读锁
            rLock.lock();
            System.out.println(Thread.currentThread().getId() + " 加读锁成功，执行业务...");
            s = redisTemplate.opsForValue().get("writeValue");
            TimeUnit.SECONDS.sleep(30);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
            System.out.println(Thread.currentThread().getId() + " 释放读锁...");
            rLock.unlock();
        }
        return s;
    }

    /**
     * 读写锁：写锁
     */
    @GetMapping("/write")
    public String writeValue() {
        RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
        RLock rLock = lock.writeLock();
        String s = IdUtil.getSnowflakeNextIdStr();
        try {
            // 加写锁
            // 1、改数据加写锁，读数据加读锁
            rLock.lock();
            System.out.println(Thread.currentThread().getId() + " 加写锁成功，执行业务...");
            TimeUnit.SECONDS.sleep(30);
            redisTemplate.opsForValue().set("writeValue", s);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
            System.out.println(Thread.currentThread().getId() + " 释放写锁...");
            rLock.unlock();
        }
        return s;
    }

    /**
     * 信号量（Semaphore）
     * <p>
     * 基于Redis的Redisson的分布式信号量（Semaphore）Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法
     */
    @GetMapping("/semaphore")
    public String Semaphore() throws InterruptedException {
        RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
        // 加锁
        semaphore.acquire();
        // 或
        semaphore.acquireAsync();
        semaphore.acquire(23);
        semaphore.tryAcquire();
        // 或
        semaphore.tryAcquireAsync();
        semaphore.tryAcquire(23, TimeUnit.SECONDS);

        // 释放锁
        // 或
        semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
        semaphore.release(10);
        semaphore.release();
        // 或
        semaphore.releaseAsync();
        return "成功";
    }

    /**
     * 闭锁（CountDownLatch）
     * <p>
     * 基于Redisson的Redisson分布式闭锁（CountDownLatch）Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。
     */
    @GetMapping("/cdl")
    public String countDownLatch() throws InterruptedException {
        RCountDownLatch latch1 = redissonClient.getCountDownLatch("anyCountDownLatch");
        latch1.trySetCount(1);
        latch1.await();// 在其他线程或其他JVM里

        RCountDownLatch latch2 = redissonClient.getCountDownLatch("anyCountDownLatch");
        latch2.countDown();
        return "成功";
    }
}
